# 1. 导入库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import os
from sqlalchemy import create_engine
from matplotlib.font_manager import FontProperties  # 用于加载本地中文字体
plt.rcParams["font.family"] = ["SimHei", "SimSun", "PingFang SC", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示异常
plt.rcParams["font.size"] = 10

# 2. 局部字体配置（指定本地字体文件路径）
try:
    # Windows字体路径
    chinese_font = FontProperties(fname="C:/Windows/Fonts/simhei.ttf", size=10)
    print("✅ 成功加载Windows中文字体（SimHei）")
except Exception as e:
    try:
        chinese_font = FontProperties(fname="C:/Windows/Fonts/msyh.ttc", size=10)
        print("✅ 成功加载Windows中文字体（微软雅黑）")
    except:
        print(f"⚠️  本地字体加载失败：{str(e)}，中文可能显示异常")
        print("解决方案：1.Windows：确认C:/Windows/Fonts/simhei.ttf存在；")
        chinese_font = FontProperties(size=10)  # 兜底：用默认字体

sns.set_style("whitegrid")  # 中文执行后配置，避免覆盖

# 3. 数据库配置（敏感信息脱敏）
db_config = {
    "host": "【数据库地址占位符】",
    "user": "【数据库用户名占位符】",
    "password": "******",
    "database": "lendingclub_loan_db_v1",
    "port": 3306,
    "table_name": "loan_2018_q1"
}

# 4. 设置图片保存路径
save_dir = "【图表保存路径占位符】" 
if not os.path.exists(save_dir):
    try:
        os.makedirs(save_dir)
        print(f"✅ 自动创建图表文件夹：{save_dir}")
    except Exception as e:
        print(f"❌ 文件夹创建失败：{str(e)}，请手动创建: {save_dir}")
        exit()  # 文件夹创建不了，后续无法保存，直接退出

# ------------------- 数据处理部分-------------------

# 函数1：读取数据
def load_data(engine, table_name):
    """
    从MySQL数据库读取信贷核心字段（仅保留分析必需字段，减少内存占用）
    :param engine: SQLAlchemy数据库连接引擎（需提前建立有效连接）
    :param table_name: 待读取的表名
    :return: pd.DataFrame - 读取后的数据框，含11个核心字段
    :raises SystemExit: 若字段不存在或数据库连接失败，打印错误后退出程序
    """
    needed_fields = [
        "installment", "emp_length", "home_ownership", "annual_inc", 
        "loan_status", "dti", "delinq_2yrs", "fico_range_low", 
        "inq_last_6mths", "open_acc", "revol_util"
    ]
    try:
        df = pd.read_sql_table(table_name=table_name, con=engine, columns=needed_fields)
        print(f"✅ 数据读取成功：{df.shape[0]}行×{df.shape[1]}列")
        return df
    except Exception as e:
        print(f"❌ 读取失败：{str(e)}（检查字段是否存在）")
        exit()

# 函数2：查看数据基础信息
def check_data_info(df):
    """
    探查信贷数据基础信息+关键异常值，提前暴露数据质量问题
    :param df: pd.DataFrame - 原始读取的数据框（来自load_data函数）
    :return: None - 无返回值，直接打印数据信息到终端
    :note: 核心探查逻辑（符合风控数据质量标准）：
            1. 基础信息：行数/列数、前5行预览；
            2. 缺失值：仅统计核心字段；
            3. 异常值：聚焦“影响风控结果”的字段，如：
               - 年收入≤0：无收入来源，必然逾期风险；
               - FICO分≠300-850：国际信用分标准范围外，数据无效；
               - 循环信用利用率<0或>100：逻辑错误（利用率不可能超100%）；
               - loan_status非0/1：目标变量编码错误，后续相关性分析失效。
    """
    print("\n=== 数据基础信息 ===")
    print(f"行数×列数：{df.shape}")
    print("\n前5行数据（详细预览）：")
    print(df.head()) 
    print("\n关键字段缺失值统计：")
    key_fields = [
        "installment", "emp_length", "home_ownership", "annual_inc", 
        "loan_status", "dti", "delinq_2yrs", "fico_range_low", 
        "inq_last_6mths", "open_acc", "revol_util"
    ]
    print("关键字段缺失值：\n", df[key_fields].isnull().sum())

    # 关键异常值统计（提前提示数据问题）
    print("\n关键异常值统计（后续预处理会过滤）：")
    print(f"  - 年收入为0的记录：{len(df[df['annual_inc'] <= 0])}条")
    print(f"  - FICO分超出300-850的记录：{len(df[~df['fico_range_low'].between(300, 850)])}条")
    print(f"  - 循环信用利用率异常（<0或>100）：{len(df[~df['revol_util'].between(0, 100)])}条")

    # 验证loan_status是否为二分类
    print("\n=== 目标变量（loan_status）验证 ===")
    valid_loan_status = df["loan_status"].unique()
    if not set(valid_loan_status).issubset({0, 1}):
        print(f"⚠️  警告：loan_status存在非0/1值：{valid_loan_status}，请检查SQL表编码！")
    else:
        default_rate = df["loan_status"].mean() * 100
        print(f"✅ loan_status验证通过：仅含0/1（逾期率：{default_rate:.2f}%，参考范围5%-30%）")

# 函数3：数据预处理
def preprocess_data(df):
    """
    信贷数据标准化预处理（符合风控建模数据要求）：异常值过滤→缺失值删除→新特征计算→分箱→独热编码
    :param df: pd.DataFrame - 原始数据框（来自load_data函数，已通过check_data_info探查）
    :return: tuple - (df_clean, df_encoded)，含：
        - df_clean：分箱后未编码的数据框（保留原始字段，用于交叉分析）；
        - df_encoded：独热编码后的数据框（含分箱/分类字段编码，用于相关性分析）；
    :note: 关键处理逻辑（行业风控标准）：
        1. 异常值过滤：仅保留“符合现实业务”的样本；
        2. 新特征计算：monthly_pressure=月还款额/（年收入/12）；
        3. 核心字段分箱；
        4. 独热编码：drop_first=True（避免多重共线性）。
    """
    # 1. 关键字段异常值明细（预处理前评估数据质量）
    print("\n=== 预处理前异常值统计（关键字段） ===")
    abnormal_stats = [
        ("年收入≤0", len(df[df["annual_inc"] <= 0]), df),
        ("FICO分异常（<300或>850）", len(df[~df["fico_range_low"].between(300, 850)]), df),
        ("DTI<0或>100%", len(df[~df["dti"].between(0, 100)]), df),
        ("循环信用利用率<0或>100%", len(df[~df["revol_util"].between(0, 100)]), df),
        ("未结清账户数<0或>20", len(df[~df["open_acc"].between(0, 20)]), df)
    ]
    for desc, count, data in abnormal_stats:
        ratio = (count / len(data)) * 100
        print(f"  - {desc}：{count}条（占比：{ratio:.2f}%）")
    print("注：上述异常值将在后续步骤中过滤")
    
    print("\n=== 开始预处理 ===")
    
    # 2. 过滤异常值
    print("1. 过滤异常值...")
    df_clean = df[
        (df["annual_inc"] > 0) & (df["annual_inc"] <= 1000000)  
        & (df["fico_range_low"].between(300, 850))
        & (df["dti"] >= 0) & (df["dti"] <= 100) 
        & (df["revol_util"].between(0, 100))
        & (df["open_acc"].between(0, 20))
        & (df["home_ownership"].isin(["RENT", "MORTGAGE", "OWN"]))  
    ].copy()

    before_rows, before_cols = df.shape
    after_rows, after_cols = df_clean.shape
    drop_rows = before_rows - after_rows
    abnormal_ratio = (drop_rows / before_rows) * 100  # 计算异常值占比
    print(f"  过滤前：{before_rows}行×{before_cols}列")
    print(f"  过滤后：{after_rows}行×{after_cols}列")
    print(f"  剔除异常值：{drop_rows}条（异常率：{abnormal_ratio:.2f}%）")
    invalid_homes = len(df) - len(df_clean[df_clean["home_ownership"].isin(["RENT", "MORTGAGE", "OWN"])])
    print(f"  剔除非法住房状态（非RENT/MORTGAGE/OWN）：{invalid_homes}条")
   
    # 3. 处理缺失值（保留工作年限为空，仅删除其他关键字段缺失的行）
    print("\n2. 处理缺失值...")
    # 步骤1：定义关键字段（排除工作年限emp_length，允许其为空）
    key_required_fields = [
        "installment", "home_ownership", "annual_inc", 
        "loan_status", "dti", "delinq_2yrs", "fico_range_low", 
        "inq_last_6mths", "open_acc", "revol_util"
    ]
    # 步骤2：统计处理前的基础信息
    before_rows_missing = len(df_clean) 
    # 统计“仅工作年限为空”的样本数
    only_emp_missing = df_clean[
        df_clean["emp_length"].isnull() & 
        df_clean[key_required_fields].notna().all(axis=1)  # 其他关键字段无缺失
    ].shape[0]

    # 步骤3：仅删除“其他关键字段缺失”的行，保留仅工作年限为空的行
    df_clean = df_clean.dropna(subset=key_required_fields)
    after_drop_rows = len(df_clean)
    drop_rows = before_rows_missing - after_drop_rows  

    # 步骤4：重新计算缺失值统计
    remaining_missing = df_clean["emp_length"].isnull().sum()
    print(f"  处理前总行数：{before_rows_missing}条")
    print(f"  其他关键字段缺失需删除：{drop_rows}条")
    print(f"  保留仅工作年限为空的样本：{only_emp_missing}条")
    print(f"  处理后剩余：{after_drop_rows}条（含{remaining_missing}条工作年限空值数据）")
    print(f"  处理方式：仅删除除工作年限外其他关键字段含缺失值的行，保留仅工作年限为空的行")
    
    # 4. 计算新特征
    print("\n3. 计算新特征[每月还款压力]...")     
    df_clean["monthly_pressure"] = df_clean["installment"] / (df_clean["annual_inc"] / 12)
    before_pressure = len(df_clean)
    df_clean = df_clean[df_clean["monthly_pressure"] <= 1]
    after_pressure = len(df_clean)
    drop_pressure = before_pressure - after_pressure
    print(f"计算前：{before_pressure}行 → 过滤压力>1的异常值后：{after_pressure}行（剔除{drop_pressure}条，异常率：{drop_pressure/before_pressure*100:.2f}%）")

# -------------------核心字段分箱（行业规范区间）-------------------

    print("\n4. 核心字段分箱...")
    # 5. 核心字段分箱
    # 5.1 每月还款额（installment）分箱
    df_clean["installment_bin"] = pd.cut(
        df_clean["installment"],
        bins=[0, 200, 500, 800, 1000, float("inf")],
        labels=["低(0-200元)", "较低(200-500元)" , "中(500-800元)", "较高(800-1000元)", "高(1000元+)"],
        right=False
    )
    
    # 5.2 每月还款压力（monthly_pressure）分箱
    df_clean["pressure_bin"] = pd.cut(
        df_clean["monthly_pressure"],
        bins=[0, 0.1, 0.25, 0.3, 1],
        labels=["低(0-10%)", "较低(10-25%)", "中(25-30%)", "高(30%+)"],
        right=False
    )

    # 5.3 dti分箱
    df_clean["dti_bin"] = pd.cut(
        df_clean["dti"],
        bins=[0, 10, 20, 36, float("inf")],
        labels=["低(0-10%)", "中(10-20%)", "较高(20-36%)", "高(36%+)"],
        right=False
    )

    # 5.4 FICO分箱
    df_clean["fico_bin"] = pd.cut(
        df_clean["fico_range_low"],
        bins=[660, 700, 750, 850], 
        labels=["一般(660-699)", "良好(700-749)", "优质(750-850)"],
        right=False 
    )

    # 5.5 历史逾期次数分箱
    df_clean["delinq_bin"] = pd.cut(
        df_clean["delinq_2yrs"],
        bins=[-1, 0, 2, float("inf")],
        labels=["无(0次)", "少(1-2次)", "多(3次+)"],
        right=True
    )

    # 5.6 循环信用利用率分箱
    df_clean["revol_bin"] = pd.cut(
        df_clean["revol_util"],
        bins=[0, 10, 60, 80, float("inf")],
        labels=["低(0-10%)", "中(10-60%)", "较高(60-80%)", "高(80%+)"],
        right=False
    )

    # 5.7 未结清账户数分箱
    df_clean["open_bin"] = pd.cut(
        df_clean["open_acc"], 
        bins=[0, 5, 9, 13, 20],
        labels=["账户数0-4", "账户数5-8", "账户数9-12", "账户数12+"], 
        right=False
    )

    # 5.8 工作年限分箱
    df_clean["emp_bin"] = pd.cut(
        df_clean["emp_length"],
        bins=[0, 1, 3, 10, 40],
        labels=["不足1年(无工作，新户)", "1-2年(短期工作)", "3-9年(中期工作)", "10年及以上(长期工作)"],
        right=False
    )

    # 5.9 分箱结果预览（验证分箱合理性，避免分箱异常）
    print("\n=== 分箱结果预览（前5行核心字段） ===")
    # 选择关键分箱字段+目标变量，避免打印过多列
    preview_cols = [
         "installment_bin", "pressure_bin", "dti_bin", 
         "delinq_bin", "fico_bin", "revol_bin", "open_bin",
         "emp_bin", "loan_status"
        ]
    print(df_clean[preview_cols].head())

    # 5.10 统计各分箱的样本占比（验证分箱是否均衡，无极端小样本）
    print("\n关键分箱字段样本占比（确保无<5%的极端区间）：")
    bin_fields_to_check = [
         "installment_bin", "pressure_bin", "dti_bin", 
         "delinq_bin", "fico_bin", "revol_bin", "open_bin",
         "emp_bin"
        ]
    for field in bin_fields_to_check:
        print(f"\n{field} 分箱占比：")
        bin_ratio = df_clean[field].value_counts(normalize=True).round(4) * 100
        for bin_label, ratio in bin_ratio.items():
             print(f"  - {bin_label}：{ratio:.2f}%")

    # 6. 分类变量独热编码
    print("\n5. 处理住房状态分类变量...")       
    df_encoded = pd.get_dummies(
        df_clean, 
        columns=[
            "home_ownership", "installment_bin", "pressure_bin",
            "dti_bin", "fico_bin", "delinq_bin", "revol_bin",
            "open_bin", "emp_bin"
        ], 
        drop_first=True
    )
    print(f"✅ 预处理完成，最终数据量：{df_encoded.shape}（含分箱编码变量）")

    # 判断df_clean是否为空，避免后续报错
    if df_clean.empty:
        print("❌ 预处理后数据为空（所有数据被过滤），无法进行交叉分析，程序退出")
        exit()  # 终止程序，避免后续无效执行
  
    return df_clean, df_encoded  # 返回原始分箱数据和编码数据


# ------------------- 相关性分析：1.分维度（还款能力、信用历史、稳定性）分析-------------------

def analyze_dimension(dim_name, fields, data, save_dir):
    """
    分维度相关性分析+绘图（补充字段维度映射，提升解读严谨性）
    :param dim_name: 维度名称（如“还款能力”）
    :param fields: 该维度的字段列表
    :param data: 预处理后的数据（df_encoded）
    :param dim_field_map: 字段-维度映射字典（用于严谨解读）
    :param save_dir: str - 图表保存路径（需提前创建）

    """
    print("\n" + "="*60)
    print(f"【{dim_name}维度】与逾期率（loan_status）的相关性分析")
    print("="*60)

    # 新增：仅稳定性维度筛选工作年限空值（emp_length为空的行）
    if dim_name == "稳定性":
        # 筛选emp_length非空的子集（不修改原data，仅临时使用）
        data_filtered = data[data["emp_length"].notna()].copy()
        print(f"⚠️  稳定性维度：筛选工作年限非空数据，原{len(data)}行→筛选后{len(data_filtered)}行")
    else:
        # 其他维度使用完整数据
        data_filtered = data.copy()

    # 1. 计算该维度的相关系数矩阵
    corr_cols = fields + ["loan_status"]
    corr_matrix = data_filtered[corr_cols].corr()
    # 提取与逾期率的相关性，按绝对值排序
    dim_corr = corr_matrix["loan_status"].drop("loan_status").sort_values(key=abs, ascending=False)

    # 2. 打印结果
    print(f"\n{dim_name}维度各字段与逾期率的相关性（按影响强弱）：")
    for field, corr in dim_corr.items():
        # 正负相关判断
        if corr > 0:
            trend = "正相关"
        else:
            trend = "负相关"
        
        # 行业业务解读
        if dim_name == "还款能力":
            note = {
                "installment": "（每月还款额越高，短期还款压力越大）",
                "monthly_pressure": "（还款压力超过30%为高风险，行业共识）",
                "dti": "（债务占收入比越高，可支配资金越少）"
            }.get(field, "（符合还款能力逻辑）")
        elif dim_name == "信用历史":
            note = {
                "delinq_2yrs": "（历史逾期次数越多，履约习惯越差）",
                "fico_range_low": "（FICO分≥700为良好，逾期风险低）",
                "inq_last_6mths": "(近6个月征信查询次数>3次，可能存在多头借贷风险）",
                "revol_util": "（利用率＞80%为过度负债，资金紧张风险高）",
                "open_acc": "（未结清账户数越多，可能表示用户信用历史更丰富，风险低）"
            }.get(field, "（符合信用历史逻辑）")
        else:  # 稳定性维度
            if field.startswith("home_ownership_"):
                home_type = field.replace("home_ownership_", "")
                note = f"（住房状态为{home_type}，结合收入水平判断稳定性）"
            else:
                note = {
                    "emp_length": "（工作年限越长，收入稳定性高）",
                }.get(field, "（符合稳定性逻辑）")
        
        # 打印核心信息（保留4位小数）
        print(f"- {field}：相关系数 = {corr:.4f}，{trend} {note}")

    # 3. 绘制条形图（行业规范：颜色区分正负，带数值标签）
    plt.figure(figsize=(10, 5) if len(fields) <= 3 else (12, 6))  # 按字段数量调整图大小
    colors = ["#E74C3C" if x > 0 else "#3498DB" for x in dim_corr.values]  # 红正蓝负
    
    # 少字段用水平图，多字段用垂直图（防折叠）
    if len(fields) <= 3:
        bars = sns.barplot(x=dim_corr.values, y=dim_corr.index, palette=colors)
        # 水平图：数值标签在条形两端
        for i, (val, bar) in enumerate(zip(dim_corr.values, bars.patches)):
            label_x = val + 0.005 if val > 0 else val - 0.005
            bars.text(label_x, i, f"{val:.4f}", va="center", fontsize=10)            
        plt.xlabel("相关系数", fontproperties=chinese_font)
        plt.xlim(-0.3, 0.3)  # 适配风控常见相关性范围
    else:
        bars = sns.barplot(x=dim_corr.index, y=dim_corr.values, palette=colors)
        # 垂直图：数值标签在条形顶部
        for i, (val, bar) in enumerate(zip(dim_corr.values, bars.patches)):
            label_y = val + 0.002 if val > 0 else val - 0.002
            bars.text(i, label_y, f"{val:.4f}", ha="center", fontsize=9)
        plt.xlabel("字段名称", fontproperties=chinese_font)
        plt.xticks(rotation=45, ha="right", fontproperties=chinese_font)  # 旋转标签防重叠
        plt.ylim(-0.2, 0.2)  # 适配稳定性强度弱相关特征

    # 图表通用美化（符合行业报告标准）
    plt.axvline(x=0, color="black", linestyle="--", linewidth=0.8)  # 0值参考线
    plt.title(f"{dim_name}维度各字段与逾期率的相关性", fontsize=14, pad=20, fontproperties=chinese_font)
    plt.ylabel("相关系数" if len(fields) > 3 else "字段名称", fontsize=12, fontproperties=chinese_font)
    plt.tight_layout()      #自动调整布局，避免文字截断
    # 保存路径改为save_dir
    fig_path = f"{save_dir}\\{dim_name}维度相关性.png"
    plt.savefig(fig_path, dpi=300, bbox_inches="tight")
    print(f"\n✅ {dim_name}维度图表已保存：{fig_path}\n")

# ------------------- 相关性分析：2.全局整合分析-------------------

# 1. 定义全局整合相关性分析函数
def analyze_global(dimensions, target, data, dim_field_map, save_dir):
    """
    全局相关性分析（排查共线性+确认核心字段）
    :param dimensions: 所有维度的字段字典
    :param target: 目标变量（逾期率）
    :param data: 预处理后的数据集（df_encoded）
    """
    print("="*60)
    print("【全局整合】相关性分析（技术验证）")
    print("="*60)

    # 1. 整合原始字段
    # 全局分析中，仅保留原始字段（不显示分箱编码字段），避免热力图过密
    all_fields = []
    # 定义所有分箱字段的前缀（和前代码中分箱字段命名对应）
    bin_prefixes = ["installment_bin_", "pressure_bin_", "dti_bin_", 
                "fico_bin_", "delinq_bin_", "revol_bin_", 
                "open_bin_", "emp_bin_", "home_ownership_"]

    for dim_name, fields in dimensions.items():
        # 只保留原始字段：排除所有分箱编码字段（前缀匹配上述列表）
        raw_fields = [
            f for f in fields 
            if not any(f.startswith(prefix) for prefix in bin_prefixes)
        ]
        all_fields.extend(raw_fields)
    all_fields = list(set(all_fields))  # 去重
    all_corr_cols = all_fields + [target]

    # 2. 计算全局相关系数矩阵
    print("\n1. 计算全局相关系数矩阵…")
    global_corr = data[all_corr_cols].corr()

    # 3. 打印核心信息
    print("\n2. 核心影响字段（全局排序，前5名）：")
    global_loan_corr = global_corr[target].drop(target).sort_values(key=abs, ascending=False)
    for i, (field, corr) in enumerate(global_loan_corr.head(5).items(),1):
        dim_belong = dim_field_map.get(field, "未知维度")  # 字段所属维度
        print(f"  第{i}名：{field}（{dim_belong}），相关系数 = {corr:.4f}")

    # 4. 排查共线性（行业标准：|相关系数|＞0.7为高共线性，建模需二选一）
    print("\n3. 高共线性字段排查：")
    collinear_pairs = []
    # 遍历所有字段对，避免重复
    for i in range(len(all_fields)):
        for j in range(i+1, len(all_fields)):
            field1 = all_fields[i]
            field2 = all_fields[j]
            corr_val = abs(global_corr.loc[field1, field2])
            if corr_val > 0.7:
                collinear_pairs.append((field1, field2, corr_val))
    
    if collinear_pairs:
        for field1, field2, corr_val in collinear_pairs:
            print(f"   ⚠️ {field1} 与 {field2}：相关系数 = {corr_val:.4f}（高共线性）")
    else:
        print("   ✅ 未发现高共线性字段，可直接用于后续建模")

    # 5. 绘制全局热力图（行业标准：隐藏上三角，突出重点）
    print("\n4. 绘制全局相关性热力图…")    
    plt.figure(figsize=(14, 12))
    mask = np.triu(np.ones_like(global_corr, dtype=bool))  # 隐藏上三角，避免重复

    #绘制热力图
    heatmap = sns.heatmap(
        global_corr,
        mask=mask,
        annot=True,  # 显示相关系数
        fmt=".2f",  # 保留2位小数
        annot_kws={"size": 8},  # 调整标签字体大小
        cmap="RdBu_r",  # 红正蓝负
        vmin=-0.4, vmax=0.4,  # 颜色范围适配风控常见相关性
        linewidths=0.5,  #线条分割，提升可读性
        cbar_kws={"label": "相关系数"}  # 颜色条标签
    )
    
    #单独获取颜色条对象，设置标签字体（正确的字体配置方式）
    cbar = heatmap.collections[0].colorbar  # 获取热力图的颜色条对象
    cbar.set_label(
        "相关系数",  # 颜色条标签文本
        fontproperties=chinese_font,  # 单独设置字体（支持fontproperties）
        fontsize = 12
    )
    
    #强制刻度标签字体
    ax = plt.gca()
    #设置x轴刻度标签：先获取标签文本，再指定字体
    x_labels = ax.get_xticklabels()  # 获取x轴所有刻度标签
    ax.set_xticklabels(
        [label.get_text() for label in x_labels],  # 提取标签文本
        fontproperties=chinese_font,  # 支持FontProperties，关联本地中文字体
        fontsize=10,  # 字体大小
        rotation=45,  # 旋转标签防重叠（与之前一致）
        ha="right"    # 标签右对齐（与之前一致）
    )
    # 设置y轴刻度标签
    y_labels = ax.get_yticklabels()
    ax.set_yticklabels(
        [label.get_text() for label in y_labels],
        fontproperties=chinese_font,
        fontsize=10
    )
    # 设置标题字体
    plt.title(
        "全局字段与逾期率的相关性热力图",
        fontsize=16,
        pad=30,
        fontproperties=chinese_font
    )
     
    plt.tight_layout()
    fig_path = f"{save_dir}\\全局相关性热力图.png"
    plt.savefig(fig_path, dpi=300, bbox_inches="tight")
    print(f"\n✅ 全局热力图已保存：全局相关性热力图.png")

# ------------------- 主逻辑（执行入口：顺序：连接→读取→预处理→分析）-------------------

if __name__ == "__main__":  
    try:
        print("=== LendingClub信贷数据相关性分析程序（含分箱逻辑） ===")

        # 1. 建立数据库连接（sqlalchemy自动管理连接池，无需手动关闭）
        print("\n1. 建立数据库连接...")
        engine = create_engine(
             f"mysql+pymysql://{db_config['user']}:{db_config['password']}@{db_config['host']}:{db_config['port']}/{db_config['database']}"
        )

        # 2. 依次执行核心流程（顺序不可乱）
        df = load_data(engine, db_config["table_name"])          # 读取数据
        check_data_info(df)                                      # 查看基础信息
        df_clean, df_encoded = preprocess_data(df)                          # 预处理数据
         
        # 3. 定义维度-字段字典（关键：需在df_encoded生成后定义，避免变量未定义）
        print("\n6. 定义分析维度与字段（含分箱编码字段）...")
        # 提取分箱后的独热编码字段（按前缀匹配）
        installment_bin_fields = [col for col in df_encoded.columns if col.startswith("installment_bin_")]
        pressure_bin_fields = [col for col in df_encoded.columns if col.startswith("pressure_bin_")]
        dti_bin_fields = [col for col in df_encoded.columns if col.startswith("dti_bin_")]
        fico_bin_fields = [col for col in df_encoded.columns if col.startswith("fico_bin_")]
        delinq_bin_fields = [col for col in df_encoded.columns if col.startswith("delinq_bin_")]
        revol_bin_fields = [col for col in df_encoded.columns if col.startswith("revol_bin_")]
        open_bin_fields = [col for col in df_encoded.columns if col.startswith("open_bin_")]        
        emp_bin_fields = [col for col in df_encoded.columns if col.startswith("emp_bin_")]                 
        dimensions = {
            "还款能力": [
                "installment", "monthly_pressure", "dti"  # 原字段
            ] + installment_bin_fields + pressure_bin_fields + dti_bin_fields,  # 加入分箱字段
                "信用历史": [
                "inq_last_6mths", "delinq_2yrs", "fico_range_low", "revol_util", "open_acc"
            ] + fico_bin_fields + delinq_bin_fields + revol_bin_fields + open_bin_fields, 
                "稳定性": [
                "emp_length" 
            ] + emp_bin_fields + [col for col in df_encoded.columns if col.startswith("home_ownership_")]  # 原住房状态字段
        }
        total_fields = sum(len(fields) for fields in dimensions.values())
        print(f" 已定义3个分析维度，共{total_fields}个字段")

        # 4. 执行分维度相关性分析
        print("\n7. 开始分维度相关性分析（含分箱字段）...")
        # 构建字段-维度映射（用于分析解读）
        dim_field_map = {}
        for dim_name, fields in dimensions.items():
            for field in fields:
                dim_field_map[field] = dim_name
        # 遍历所有维度执行分析
        for dim_name, fields in dimensions.items():
            analyze_dimension(dim_name, fields, df_encoded, save_dir)

        # 5. 执行全局整合分析
        print("\n 开始全局相关性分析...")
        analyze_global(dimensions, "loan_status", df_encoded, dim_field_map, save_dir)
        print("\n" + "="*60)
        print(f"=== 所有分析完成！4张图表已保存到：{save_dir} ===")
        print("生成文件：1.还款能力维度相关性.png 2.信用历史维度相关性.png 3.稳定性维度相关性.png 4.全局相关性热力图.png")

    except Exception as e:
        print(f"\n❌ 程序执行出错：{str(e)}")
        print("\n排查方向（按优先级）：")
        print("1. 数据库连接：确认密码、数据库名、表名是否正确（当前表名：loan_2018_q1）")
        print("2. 数据字段：确认表中存在所需字段（如installment、emp_length）")
        print("3. 服务状态：确认MySQL服务已启动（Win+R输入services.msc查看）")
        print("4. 库版本：若报版本错误，执行 pip install --upgrade pandas sqlalchemy 升级库")
